/*
 * @(#)RepositoryElasticsearchService.java
 * Date 2013-04-11
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.repository.service.es;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import javax.enterprise.context.ApplicationScoped;

import javax.enterprise.inject.Alternative;

import javax.inject.Inject;

import ihe.iti.xds_b._2007.ProvideAndRegisterDocumentSetRequestType;

import oasis.names.tc.ebxml_regrep.xsd.lcm._3.SubmitObjectsRequest;

import oasis.names.tc.ebxml_regrep.xsd.rim._3.ExtrinsicObjectType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.RegistryPackageType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.SlotType1;

import oasis.names.tc.ebxml_regrep.xsd.rs._3.RegistryResponseType;

import org.apache.commons.lang3.StringUtils;

import org.elasticsearch.action.bulk.BulkResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import us.hornerscorners.lamppost.atna.client.utils.AtnaUtils;

import us.hornerscorners.lamppost.exception.ServiceActionError;
import us.hornerscorners.lamppost.exception.ServiceActionException;
import us.hornerscorners.lamppost.exception.SeverityType;
import us.hornerscorners.lamppost.exception.ValidationException;

import us.hornerscorners.lamppost.ihecommon.config.IHEConfig;
import us.hornerscorners.lamppost.ihecommon.config.IHEServiceType;
import us.hornerscorners.lamppost.ihecommon.config.RepositoryServiceConfig;

import us.hornerscorners.lamppost.ihecommon.service.ElasticSearchSource;

import us.hornerscorners.lamppost.model.audit.AuditContext;
import us.hornerscorners.lamppost.model.audit.CodedValueType;

import us.hornerscorners.lamppost.model.document.DocumentContent;
import us.hornerscorners.lamppost.model.document.SubmissionSet;

import us.hornerscorners.lamppost.registry.client.RegistryClient;

import us.hornerscorners.lamppost.registry.constant.ErrorCode;
import us.hornerscorners.lamppost.registry.constant.ErrorSeverityType;
import us.hornerscorners.lamppost.registry.constant.ResponseStatus;
import us.hornerscorners.lamppost.registry.constant.SlotName;

import us.hornerscorners.lamppost.registry.model.RegistryResponse;

import us.hornerscorners.lamppost.registry.service.IRegistryPersistService;

import us.hornerscorners.lamppost.registry.transformer.model.SubmissionSetTransformer;

import us.hornerscorners.lamppost.registry.transformer.xds
    .RegistryResponseTypeTransformer;
import us.hornerscorners.lamppost.registry.transformer.xds.SlotTypeTransformer;

import us.hornerscorners.lamppost.registry.utils.XDSUtils;

import us.hornerscorners.lamppost.repository.inject.RepositoryTarget;
import us.hornerscorners.lamppost.repository.service.AtnaRepositoryService;

import us.hornerscorners.lamppost.repository.service.IRepositoryPersistService;

import us.hornerscorners.lamppost.repository.transformer.model.DocumentContentTransformer;


/**
 * Class description
 *
 *
 * @version        v1.0, 2013-04-11
 * @author         Jim Horner
 */
@ApplicationScoped
@Alternative
public class RepositoryPersistService implements IRepositoryPersistService {

    @Inject
    private AtnaRepositoryService atnaService;

    /** Field description */
    @Inject
    private IHEConfig config;

    /** Field description */
    @Inject
    private DocumentContentTransformer documentContentTransformer;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Field description */
    @Inject
    @RepositoryTarget
    private RegistryClient registryClient;

    /** Field description */
    @Inject
    private IRegistryPersistService registryPersistService;

    /** Field description */
    @Inject
    @RepositoryTarget
    private RegistryResponseTypeTransformer registryResponseTransformer;

    /** Field description */
    private RepositoryServiceConfig serviceConfig;

    /** Field description */
    @Inject
    private SlotTypeTransformer slotTransformer;

    /** Field description */
    @Inject
    private ElasticSearchSource source;

    /** Field description */
    @Inject
    private SubmissionSetTransformer submissionSetTransformer;

    /**
     * Constructs ...
     *
     */
    public RepositoryPersistService() {

        super();
    }

    /**
     * ITI-41 Provide and Register Document Set-b
     *
     * Follows 3.42.6 Actor Requirements (IMHO should be in 3.41 section)
     * Make all new documents available for retrieval via the Retrieve Document
     * Set transaction before initiating the Register Document Set-b Request
     * with the Registry actor.
     *
     * @param body
     *
     * @return
     *
     */
    @Override
    public RegistryResponseType persistDocumentSet(AuditContext auditContext,
            ProvideAndRegisterDocumentSetRequestType body) {

        RegistryResponseType result = null;

        SubmitObjectsRequest sor = body.getSubmitObjectsRequest();
        RegistryPackageType regpack = XDSUtils.findSubmissionSet(sor);
        SubmissionSet subset = null;

        try {

            // updates repositoryUniqueId, hash, size values
            updateSubmitObjectsRequest(body);

            if (regpack != null) {

                subset = this.submissionSetTransformer.transform(regpack);
            }

        } catch (ServiceActionException e) {

            logger.error(e.getMessage(), e);
            result = this.registryResponseTransformer.transform(
                new RegistryResponse(ResponseStatus.Failure, e));
        }

        // createDocuments()
        List<DocumentContent> documents = new ArrayList<>();

        if (result == null) {

            try {

                documents.addAll(this.documentContentTransformer.transform(body));

                // TODO validateDocuments()

            } catch (ServiceActionException e) {

                logger.error(e.getMessage(), e);
                result = this.registryResponseTransformer.transform(
                    new RegistryResponse(ResponseStatus.Failure, e));
            }
        }

        // write document contents
        BulkResponse bulkResponse = null;

        if (result == null) {

            try {

                bulkResponse = this.source.writePersistables(documents);

                if (bulkResponse.hasFailures()) {

                    this.source.rollbackTransaction(bulkResponse);
                    result = this.registryResponseTransformer.transform(
                        ResponseStatus.Failure, getClass(), ErrorCode.XDSRepositoryError,
                        bulkResponse.buildFailureMessage(), ErrorSeverityType.Error);
                }

            } catch (ServiceActionException e) {

                logger.error(e.getMessage(), e);
                result = this.registryResponseTransformer.transform(
                    new RegistryResponse(ResponseStatus.Failure, e));
            }
        }

        // register document metadata

        if (result == null) {

            try {

                RegistryResponseType regResponse = registerDocumentSet(auditContext, sor);

                if (ResponseStatus.Failure.getCode().equals(regResponse.getStatus())) {

                    this.source.rollbackTransaction(bulkResponse);
                    result = this.registryResponseTransformer.transform(
                        new RegistryResponse(ResponseStatus.Failure));
                    result.setRegistryErrorList(regResponse.getRegistryErrorList());

                } else {

                    result = this.registryResponseTransformer.transform(
                        new RegistryResponse(ResponseStatus.Success));
                }

                this.source.flush();

            } catch (ServiceActionException e) {

                result = this.registryResponseTransformer.transform(
                    new RegistryResponse(ResponseStatus.Failure, e));
            }
        }

        this.atnaService.sendPersistReceivedAuditMessage(auditContext, subset, result);

        return result;
    }

    /**
     * Method description
     *
     */
    @PostConstruct
    public void postConstruct() {

        this.serviceConfig = (RepositoryServiceConfig) this.config.findServiceConfig(
            IHEServiceType.Repository);
    }

    /**
     * Method description
     *
     *
     * @param sor
     *
     * @return
     */
    private RegistryResponseType registerDocumentSet(AuditContext auditContext,
            SubmitObjectsRequest sor) {

        RegistryResponseType result = null;

        CodedValueType eventType = AtnaUtils.createCodedValue("ITI-42",
                                       "IHE Transactions", "Register Document Set-b");

        AuditContext newContext = new AuditContext(eventType,
                                      this.config.getClientConfig(),
                                      auditContext.getTransactionContext(),
                                      auditContext.getAddressContext());

        if (this.serviceConfig.isBypassRegistryWebservice()) {

            result = this.registryPersistService.registerDocumentSet(newContext, sor);

        } else {

            result = this.registryClient.registerDocumentSet(newContext, sor);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param registryClient
     */
    protected void setRegistryClient(RegistryClient registryClient) {
        this.registryClient = registryClient;
    }

    /**
     * Method description
     *
     *
     * @param source
     */
    protected void setSource(ElasticSearchSource source) {
        this.source = source;
    }

    /**
     * Method description
     *
     *
     * @param request
     * @throws us.hornerscorners.lamppost.exception.ValidationException
     */
    protected void updateSubmitObjectsRequest(
            ProvideAndRegisterDocumentSetRequestType request)
            throws ValidationException {

        Map<String, ExtrinsicObjectType> extrinsicObjects =
            XDSUtils.createDocumentEntryMap(request.getSubmitObjectsRequest());

        // need to create
        // repositoryUniqueId, size and hash attributes

        for (ProvideAndRegisterDocumentSetRequestType.Document doc :
                request.getDocument()) {

            String key = doc.getId();
            ExtrinsicObjectType eo = extrinsicObjects.get(key);

            if (eo != null) {

                Map<SlotName, SlotType1> slotObjects = XDSUtils.createSlotMap(eo,
                                                           SlotName.Size, SlotName.Hash);

                byte[] content = doc.getValue();

                SlotType1 slotSize = slotObjects.get(SlotName.Size);
                String size = Integer.toString(content.length);

                if (slotSize != null) {

                    // need to check size matches
                    String reqSize =
                        XDSUtils.parseFirstValue(slotObjects.get(SlotName.Size));

                    if (StringUtils.equalsIgnoreCase(size, reqSize) == false) {

                        String msg =
                            String.format(
                                "Size for document %s does not match computed size of %s.",
                                doc.getId(), size);

                        ServiceActionError error =
                            new ServiceActionError(
                                ErrorCode.XDSRepositoryMetadataError.getCode(), msg,
                                SeverityType.Error);

                        throw new ValidationException(getClass(), error);
                    }
                } else {

                    eo.getSlot().add(this.slotTransformer.transform(SlotName.Size, size));
                }

                String hash = XDSUtils.calculateHash(content);
                SlotType1 slotHash = slotObjects.get(SlotName.Hash);

                if (slotHash != null) {

                    // need to check hash matches
                    String reqHash =
                        XDSUtils.parseFirstValue(slotObjects.get(SlotName.Hash));

                    if (StringUtils.equalsIgnoreCase(hash, reqHash) == false) {

                        String msg =
                            String.format(
                                "Hash for document %s does not match computed hash of %s.",
                                doc.getId(), hash);

                        ServiceActionError error =
                            new ServiceActionError(
                                ErrorCode.XDSRepositoryMetadataError.getCode(), msg,
                                SeverityType.Error);

                        throw new ValidationException(getClass(), error);
                    }

                } else {

                    eo.getSlot().add(this.slotTransformer.transform(SlotName.Hash, hash));
                }

                eo.getSlot().add(
                    this.slotTransformer.transform(
                        SlotName.RepositoryUniqueId, this.serviceConfig.getId()));
            }
        }

    }
}
